package com.guozinhs.auto.core;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.guozinhs.auto.data.AgConstant;
import com.guozinhs.auto.data.AgData;
import com.guozinhs.auto.data.AgDataManager;
import com.guozinhs.auto.frame.ActType;
import com.guozinhs.auto.log.Logger;
import com.guozinhs.auto.match.MyImageMatcher;
import com.guozinhs.auto.match.PointInfo;
import com.guozinhs.auto.screen.ScreenCapture;
import com.guozinhs.auto.script.ScriptHandler;
import com.guozinhs.auto.util.image.ImageUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 手游护肝工具
 *
 * @Author: GuoZi
 * @Date: 2022/1/7 21:30
 */
public class AutoGameExecutor implements Runnable, ExecState {

    private int executeCount = 1;
    private String startDate;
    private ScriptHandler scriptHandler;
    private ScreenCapture screenCapture;
    private Long startNodeId;

    private Thread execThread;
    private final Logger log;
    private final AgDataManager dataManager;
    private final Map<String, Component> componentMap;

    private boolean running = false;
    private boolean suspended = false;

    private StateNode startNode;
    private StateNode locationNode;

    /**
     * 默认延迟时间(单位:ms)
     */
    public static final Long DEFAULT_DELAY = 500L;

    public AutoGameExecutor(Logger log, AgDataManager dataManager, Map<String, Component> componentMap) {
        this.log = log;
        this.dataManager = dataManager;
        this.componentMap = componentMap;
        this.init();
    }

    @Override
    public void run() {
        try {
            this.exec();
            log.info("执行完毕");
        } catch (Exception e) {
            log.error("执行异常", e);
        } finally {
            this.startNodeId = null;
            this.shutdown();
        }
    }

    /**
     * 初始化数据
     */
    private void init() {
        // 创建机器人对象
        Robot robot;
        try {
            robot = new Robot();
        } catch (AWTException e) {
            log.error("初始化异常, Robot创建失败", e);
            throw new RuntimeException("Robot创建失败");
        }
        // 创建截屏对象
        this.screenCapture = new ScreenCapture(robot, this.log);
        this.scriptHandler = new ScriptHandler(robot, this.log, this);
    }

    /**
     * 执行脚本
     */
    private void exec() {
        if (Objects.isNull(this.startNodeId)) {
            throw new RuntimeException("未指定执行节点");
        }
        StateNode startState = this.dataManager.getById(this.startNodeId);
        StateNode locationNode;
        if (StateType.location.equals(startState.getStateType())) {
            locationNode = startState;
            startState = this.dataManager.getById(locationNode.getParentId());
        } else {
            locationNode = this.dataManager.getLocationNode(this.startNodeId);
        }
        this.startNode = startState;
        this.locationNode = locationNode;
        String imagePath = this.dataManager.getImagePath(locationNode.getId());
        boolean isLocation = this.screenCapture.locateWindow(locationNode, imagePath);
        if (!isLocation) {
            log.info("窗口定位失败");
            return;
        }
        long startTimeMillis = System.currentTimeMillis();
        this.startDate = DateUtil.format(new Date(startTimeMillis), "yyyy-MM-dd HH_mm_ss");
        log.info("开始时间: {}", DateUtil.formatDateTime(new Date(startTimeMillis)));
        this.executeCount = 0;
        // 开始执行
        this.execState(startState);
        long end = System.currentTimeMillis();
        String between = DateUtil.formatBetween(end - startTimeMillis);
        log.info("结束时间: {}", DateUtil.formatDateTime(new Date(end)));
        log.info("耗时: {}", between);
    }

    /**
     * 执行单个节点
     *
     * @param node 节点信息
     * @return 执行结果
     */
    private boolean execState(StateNode node) {
        // 执行当前节点
        int failCount = 0;
        int successCount = 0;
        boolean isMatch;
        boolean isCycle = Boolean.TRUE.equals(node.getIsCycle());
        boolean isForced = Boolean.TRUE.equals(node.getIsForced());
        boolean cycleOnce;
        do {
            cycleOnce = isForced;
            String stateName = node.getName();
            if (isCycle) {
                log.info("[%04d] [执行第%d次] %s", this.executeCount, ++successCount, stateName);
            } else {
                log.info("[%04d] [执行单次] %s", this.executeCount, stateName);
            }
            do {
                isMatch = this.handleState(node);
                if (isMatch) {
                    // 强制匹配, 直到匹配成功
                    failCount = 0;
                    cycleOnce = false;
                } else if (isForced) {
                    log.info("[%04d] [重试第%d次] %s", this.executeCount, ++failCount, stateName);
                }
                if (isMatch) {
                    // 处理后续节点
                    List<StateNode> next = this.dataManager.getChildren(node.getId(), StateType.normal);
                    if (ObjectUtil.isNotEmpty(next)) {
                        for (StateNode nextState : next) {
                            this.execState(nextState);
                        }
                    }
                }
            } while (cycleOnce);
        } while (isMatch && isCycle);
        // 执行完毕
        return isMatch;
    }

    /**
     * 执行多个节点
     *
     * @param stateList 节点集合
     * @return 执行结果
     */
    private boolean execState(List<StateNode> stateList) {
        if (ObjectUtil.isEmpty(stateList)) {
            return true;
        }
        for (StateNode checkState : stateList) {
            boolean execResult = this.execState(checkState);
            if (execResult) {
                return true;
            }
        }
        return false;
    }

    /**
     * 处理节点
     *
     * @param node 节点信息
     * @return 处理成功与否
     */
    private boolean handleState(StateNode node) {
        // 前置校验
        List<StateNode> preCheckStates = this.dataManager.getChildren(node.getId(), StateType.preCheck);
        boolean preCheckSuccess = this.execState(preCheckStates);
        boolean preCheckSkip = Boolean.TRUE.equals(node.getPreCheckSkip());
        if (preCheckSuccess && preCheckSkip) {
            return false;
        }
        String script = node.getScript();
        // 匹配前延迟
        this.delay(node.getPreDelay());
        Rectangle matchArea = this.match(node);
        if (matchArea == null) {
            // 匹配失败, 不执行脚本
            return false;
        }
        log.appendInfo(" [操作:{}]", script);
        Rectangle runningArea = this.screenCapture.getRange();
        // 执行脚本操作
        this.scriptHandler.handle(script, runningArea, matchArea);
        // 操作后延迟
        this.delay(node.getPostDelay());
        // 后置校验
        List<StateNode> postCheckStates = this.dataManager.getChildren(node.getId(), StateType.postCheck);
        boolean postCheckSuccess = this.execState(postCheckStates);
        boolean postCheckSkip = Boolean.TRUE.equals(node.getPostCheckSkip());
        return !postCheckSuccess || !postCheckSkip;
    }

    /**
     * 匹配当前节点
     *
     * @param node 节点
     * @return 匹配区域
     */
    private Rectangle match(StateNode node) {
        String targetImage = this.dataManager.getImagePath(node.getId());
        File targetImageFile = new File(targetImage);
        Double matchPercent = node.getMatchPercent();
        if (matchPercent <= 0 || !targetImageFile.exists()) {
            // 无需匹配图像
            log.appendInfo(" [跳过图像匹配]");
            return this.screenCapture.getRange();
        }
        // 匹配图像
        BufferedImage source = this.screenCapture.capture();
        BufferedImage template = ImageUtils.read(targetImage);
        PointInfo matchResult = MyImageMatcher.match(source, template);
        double matchValue = matchResult.getMatchPercent();
        boolean isMatched = matchValue >= matchPercent;
        if (isMatched) {
            log.appendInfo(" [匹配成功:%.2f]", matchValue);
        } else {
            log.appendInfo(" [匹配失败:%.2f]", matchValue);
        }
        Point matchPoint = matchResult.getPoint();
        // 保存匹配日志
        boolean enableImageLog = Boolean.TRUE.equals(this.dataManager.getData().getEnableImageLog());
        if (enableImageLog) {
            File file = new File(AgConstant.PATH_BASE_LOG + File.separator + this.startDate);
            if (!file.exists()) {
                file.mkdirs();
            }
            File imageFile = new File(file, String.format("[%04d]%s_%.2f.png", this.executeCount, node.getName(), matchValue));
            MyImageMatcher.drawMatch(source, template, matchPoint, imageFile.getAbsolutePath());
        }
        this.executeCount++;
        if (!isMatched) {
            return null;
        }
        return this.getMatchArea(matchPoint, template);
    }

    /**
     * 计算匹配范围
     *
     * @param matchPoint 匹配坐标
     * @param template   模板图片数据
     * @return 匹配范围
     */
    private Rectangle getMatchArea(Point matchPoint, BufferedImage template) {
        return new Rectangle(
                (int) (matchPoint.x + this.screenCapture.getRange().getX()),
                (int) (matchPoint.y + this.screenCapture.getRange().getY()),
                template.getWidth(),
                template.getHeight());
    }

    /**
     * 等待延迟
     *
     * @param delayMs 等待时间(单位:ms)
     */
    private void delay(Long delayMs) {
        this.waitSuspend();
        if (this.isShutdown()) {
            throw new RuntimeException("停止当前线程");
        }
        if (delayMs == null || delayMs < 0) {
            delayMs = DEFAULT_DELAY;
        }
        try {
            Thread.sleep(delayMs);
        } catch (InterruptedException ite) {
            ite.printStackTrace();
        }
        if (this.isShutdown()) {
            throw new RuntimeException("停止当前线程");
        }
    }

    /**
     * 等待运行
     */
    public void waitSuspend() {
        while (this.isSuspended()) {
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException ite) {
                throw new RuntimeException("执行结束");
            }
        }
    }

    @Override
    public boolean isSmoothMove() {
        return Boolean.TRUE.equals(this.locationNode.getSmoothMove());
    }

    @Override
    public int getSmoothMoveSpeed() {
        return this.locationNode.getSmoothMoveSpeed();
    }

    @Override
    public boolean isRandomMove() {
        return Boolean.TRUE.equals(this.locationNode.getRandomMove());
    }

    @Override
    public int getRandomMoveRadius() {
        return this.locationNode.getRandomMoveRadius();
    }

    @Override
    public AgData getGlobalData() {
        return this.dataManager.getData();
    }

    @Override
    public boolean isShutdown() {
        return !this.running;
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }

    @Override
    public boolean isSuspended() {
        return this.suspended;
    }

    @Override
    public void run(Long stateNodeId) {
        this.running = true;
        this.suspended = false;
        // 改变按钮样式
        JButton runButton = (JButton) this.componentMap.get(AgConstant.KEY_BUTTON_RUN);
        JButton stopButton = (JButton) this.componentMap.get(AgConstant.KEY_BUTTON_STOP);
        runButton.setActionCommand(ActType.suspend.name());
        runButton.setText("暂停");
        stopButton.setEnabled(true);
        // 开启执行线程
        this.startNodeId = stateNodeId;
        this.execThread = new Thread(this);
        this.execThread.start();
    }

    @Override
    public void shutdown() {
        this.running = false;
        this.suspended = false;
        // 改变按钮样式
        JButton runButton = (JButton) this.componentMap.get(AgConstant.KEY_BUTTON_RUN);
        JButton stopButton = (JButton) this.componentMap.get(AgConstant.KEY_BUTTON_STOP);
        runButton.setActionCommand(ActType.run.name());
        runButton.setText("启动");
        stopButton.setEnabled(false);
        // 终止执行的线程
        this.execThread.interrupt();
    }

    @Override
    public void suspend() {
        this.suspended = true;
        // 改变按钮样式
        JButton runButton = (JButton) this.componentMap.get(AgConstant.KEY_BUTTON_RUN);
        runButton.setActionCommand(ActType.continued.name());
        runButton.setText("继续");
    }

    @Override
    public void continued() {
        this.suspended = false;
        // 改变按钮样式
        JButton runButton = (JButton) this.componentMap.get(AgConstant.KEY_BUTTON_RUN);
        runButton.setActionCommand(ActType.suspend.name());
        runButton.setText("暂停");
    }

}
